void printf(const char *format, ...);
char *remap(char *a, int  b);
void outl(unsigned int s, unsigned int pnum);
void outw(unsigned short s, unsigned int pnum);
void outb(unsigned char s, unsigned int pnum);
unsigned int inl(unsigned int pnum);
unsigned short inw(unsigned int pnum);
unsigned char inb(unsigned int pnum);

/** Simple PCI device description */
struct pci_dev
{
		/** Header */
		union
		{
				struct
				{
						unsigned short vendor_id;
						unsigned short dev_id;
				} struct_r0;
				unsigned int raw_r0;
		} r0;

		union
		{
				struct
				{
						unsigned short cmd;
						unsigned short status;
				} struct_r1;
				unsigned int raw_r1;
		} r1;
		union
		{
				struct
				{
						unsigned char rev_id;
						unsigned char prog_if;
						unsigned char subclass;
						unsigned char classcode;
				} struct_r2;
				unsigned int raw_r2;
		} r2;
		union
		{
				struct
				{
						unsigned char cachesiz;
						unsigned char latency;
						unsigned char type;
						unsigned char BIST;
				} struct_r3;
				unsigned int raw_r3;
		} r3;

		unsigned int bar[6];
		unsigned int cardbus;
		unsigned int subsys;
		unsigned int rombar;
		unsigned int capabilities;
		unsigned int reserved;
		union
		{
				struct
				{
						unsigned char irq;
						unsigned char pin;
						unsigned char min_grant;
						unsigned char max_latency;
				} irq_info;
				unsigned int irq_reg;
		} interrupts;

		/* bus device number */
		int bus_num;
		/* device number on that bus */
		int dev_num;
		int func_num;

		/* Memory in IO address space. Can be accsessed directly: inb, outb... */
		unsigned long io_mem;
		/* Mapped memory (not working right now) */
		unsigned long map_mem;

		/* Device 'body' */
		void *irq_handler;
		/* private device data */
		void *priv;
};

/** PCI Configuration Space Address */
#define	PCI_CONF_ADR	0x0CF8
/** PCI Configuration Space Data */
#define	PCI_CONF_DATA	0x0CFC

#define PCI_DEV_MAX_FUNCS	8
#define PCI_CONF_HDR_MULTIFUNC	0x80

/* PCI Configuration Address Space Layout (parts) */
#define PCI_COMMAND		0x04	/* 16 bits */
#define PCI_COMMAND_MASTER	0x4	/* Enable bus mastering */

#define	PCI_NODEV	0xFFFF

#define PCI_CONF_HDR_DEVREG		0x0
#define PCI_CONF_HDR_CMDREG		0x4
#define PCI_CONF_HDR_CLASSREG		0x8
#define PCI_CONF_HDR_TYPEREG		0xC

#define PCI_CONF_BAR_MAX	6
#define PCI_CONF_HDR_BAR0	0x10
#define PCI_CONF_HDR_BAR1 	0x14
#define PCI_CONF_HDR_BAR2 	0x18
#define PCI_CONF_HDR_BAR3 	0x1C
#define PCI_CONF_HDR_BAR4 	0x20
#define PCI_CONF_HDR_BAR5 	0x24
#define PCI_CONF_HDR_IRQ	0x3C


#define ATA_PRIMARY_CMD_IO	0x1F0
#define ATA_PRIMARY_CTRL_IO	0x3F4
#define ATA_SECNDRY_CMD_IO	0x170
#define ATA_SECNDRY_CTRL_IO	0x374

#define ATA_DATA_REG		0x0
#define ATA_ERROR_REG		0x1
#define ATA_SECT_COUNT_REG	0x2
#define ATA_SECT_NUM_REG	0x3
#define ATA_CYL_LOW_REG		0x4
#define ATA_CYL_HIGH_REG	0x5
#define ATA_DRIVE_SEL_REG	0x6
#define ATA_STAT_CMD_REG	0x7

#define PII_BMICOM1	0x0		// command register
#define PII_BMISTA1	0x2		// status register
#define PII_BMIDTP1	0x0		// Descriptor pointer register

static struct pci_dev pdevs[0x400];
static unsigned int iobase;

static void
setMaster(struct pci_dev *pdev)
{
	int busdev;
	if (!pdev) return;
	busdev = (pdev->bus_num << 16)|(pdev->dev_num << 11)|(1 << 31);
	outl(busdev + PCI_COMMAND, (int)iobase + PCI_CONF_ADR);
	outw(PCI_COMMAND_MASTER, (int)iobase + PCI_CONF_ADR);
}

struct dma_descriptor {

} ;

static void
detectIde(struct pci_dev *pdev)
{
	int i;
	char data[2];
	int d_buffer[128];
	unsigned char seccnt;

	if (!pdev) return;

	pdev->bar[0] = pdev->bar[0] ? pdev->bar[0] : ATA_PRIMARY_CMD_IO;
	pdev->bar[1] = pdev->bar[1] ? pdev->bar[1] : ATA_PRIMARY_CTRL_IO;
	pdev->bar[2] = pdev->bar[2] ? pdev->bar[2] : ATA_SECNDRY_CMD_IO;
	pdev->bar[3] = pdev->bar[3] ? pdev->bar[3] : ATA_SECNDRY_CTRL_IO;

	if (!iobase) {
		printf("Invalid IOBASE!!!!!!!!!!!\n");
		return;
	}

	setMaster(pdev);

	seccnt = 1;

	/* select 0 drive LBA mode */
	outb(0xE0,pdev->bar[0] + ATA_DRIVE_SEL_REG);
	/* set sector count */
	outb(seccnt,pdev->bar[0] + ATA_SECT_COUNT_REG);
	/* set starting sector number */
	outb(0,pdev->bar[0] + ATA_SECT_NUM_REG);
	/* set cylinder (low byte) */
	outb(0,pdev->bar[0] + ATA_CYL_LOW_REG);
	/* set cylinder (high byte) */
	outb(0,pdev->bar[0] + ATA_CYL_HIGH_REG);
	/* set command */
	outb(0x20,pdev->bar[0] + ATA_STAT_CMD_REG);

	while(!(inb(pdev->bar[0] + ATA_STAT_CMD_REG) & 8));

	for ( i=0; i < (seccnt*512) / 2; i++)
	{
		*(unsigned short *)data = inw(pdev->bar[0] + ATA_DATA_REG);
		putchar(data[0]);
		putchar(data[1]);
	}

	return;
}


/**
 *
 */
int main(void *data)
{
	int busdev;
	int busnum;
	int devnum;
	int barnum;
	int pcidat;
	int header;
	int vendor;
	int device;
	int fun, funcs = 1;
	int busdevfun;
	struct pci_dev pdev;
	int *qqq = (int *)0x23482;

	iobase = (unsigned int)remap((unsigned int)0x0, (int)0x1000);

	for (busnum = 0; busnum < (1 << 8); busnum++)
	{

		for (devnum = 0; devnum < (1 << 5); devnum++)
		{

			/* FIXME buffer overflow ? */
			busdev = (busnum << 16) | (devnum << 11) | (1 << 31);
			outl(busdev, (iobase + PCI_CONF_ADR));
			pcidat = inl(iobase + PCI_CONF_DATA);

			vendor = pcidat & 0x0000FFFF;
			if (vendor == PCI_NODEV) continue;

			device = pcidat >> 16;
			outl(busdev | PCI_CONF_HDR_TYPEREG, (iobase + PCI_CONF_ADR));
			header = (inl((iobase + PCI_CONF_DATA)) >> 16) & 0xFF;

			/*
			 * Multifunction device!
			 */
			if (header & PCI_CONF_HDR_MULTIFUNC)
			{
				funcs = PCI_DEV_MAX_FUNCS;
			}

			/*
			 * Enumerate each function
			 */
			for (fun = 0; fun < funcs; fun++) {

				busdevfun = busdev | (fun << 8);
				outl(busdevfun, (iobase + PCI_CONF_ADR));
				pdev.r0.raw_r0 = (unsigned int)inl((iobase + PCI_CONF_DATA));

				if (pdev.r0.struct_r0.vendor_id == PCI_NODEV) continue;

				pdev.bus_num = busnum;
				pdev.dev_num = devnum;
				pdev.func_num = fun;

				/* read device class */
				outl((busdevfun | PCI_CONF_HDR_CLASSREG), (iobase + PCI_CONF_ADR));
				pdev.r2.raw_r2 = inl(iobase + PCI_CONF_DATA);

				/* Read IRQ */
				outl((busdevfun | PCI_CONF_HDR_IRQ), (iobase + PCI_CONF_ADR));
				pdev.interrupts.irq_reg = inl(iobase + PCI_CONF_DATA);

				/* Print out device info */
				printf("%d:%d.%d Vend[0x%x] Dev[0x%x] DevClass[0x%x] [rev.%d]\n",
						busnum,devnum,fun,
						pdev.r0.struct_r0.vendor_id,
						pdev.r0.struct_r0.dev_id,
						pdev.r2.struct_r2.classcode,
						pdev.r2.struct_r2.rev_id);

				/* read bars */
				for (barnum = 0; barnum < PCI_CONF_BAR_MAX; barnum++)
				{
					outl(busdevfun | (PCI_CONF_HDR_BAR0 + (barnum << 2)), (iobase + PCI_CONF_ADR));
					pdev.bar[barnum] = inl(iobase + PCI_CONF_DATA);
					printf("BAR=0x%x\n",pdev.bar[barnum]);
				}
				if (pdev.r2.struct_r2.classcode == 0x1)
					detectIde(&pdev);
				// ndevs++;
			}
		}
	}

	*qqq = 0x44556677;

	busdevfun = *qqq;

	printf("Reading wrote memory = 0x%x\n",*qqq);

	for (;;) {}

	return 0;
}
